﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClusterAggregation.ClusterAggregators
{
    using Datum;
    using DataSets;
    using Clusterers;
    /**
     * Implementation for Meta CLustering Algorithm
     * thoroughly explained at ( http://strehl.com/diss/node82.html )
     */
    public class CMCLA : IConsensus
    {
        /** base distance matrix */
        private double[,] m_distancesMatrix;
        /** the metadata - after transformation */
        private AData[] m_metadata;
        /** the array of original clusters */
        private CCluster[] m_clsarray;
        /** clusterrer for meta-clustering */
        private IClusterer m_clusterer;
        /** meta clusters created */
        private CCluster[] m_metaClusters;
        /** affiliation of data to clusters */
        private uint[,] m_dataAfilliation;
        /** a referance to the original data */
        private AData[] m_data;
        /** the resulting partition */
        private CPartition m_resultPartition;

        /** 
         * basic constructor 
         * @param clusterer to be used for meta-clustering. default (or null) is Spectral Clustering ( CSpectralClusterer ) with 0.67 as smoothing param
         */
        public CMCLA(IClusterer clstrr = null)
        {
            m_clusterer = clstrr;
            if (clstrr == null)
                //m_clusterer = new CPamImpl();
             m_clusterer = new CSpectralClusterer(null, 0.4, true);
        }


        public CPartition ensemble(CPartition[] partitions, AData[] data, int k)
        {
            lock (this)
            {
                // initializations
                m_data = data;
                m_resultPartition = new CPartition();
                m_resultPartition.name = "MCLA";

                for (int i = 0; i < k; i++)
                {
                    m_resultPartition.clusters.Add(new CCluster());
                }
                // end of initializations

                // algorithm by steps - as defined by strehl and gosh
                createHyperGraph(partitions);
                clusterHyperEdges(k);
                collapseHyperEdges();
                competeForObjects();

                //release data
                CPartition rez = m_resultPartition;
                m_distancesMatrix = null;
                m_metadata = null;
                m_clsarray = null;
                m_metaClusters = null;
                m_dataAfilliation = null;
                m_data = null;
                m_resultPartition = null;
                rez.ANMI = rez.compareTo(partitions);
                return rez;
            }
        }


        /**
         * First stage is to create the hyper graph.
         * @param partitions (CPartition[]) [IN] the partitions
         */
        private void createHyperGraph(CPartition[] partitions)
        {

            // join clusters to list
            LinkedList<CCluster> m_clusters = new LinkedList<CCluster>();

            // accumulate clusters
            for (int i = 0; i < partitions.Length; i++)
            {
                for (int j = 0; j < partitions[i]._clusters.Count; j++)
                {
                    m_clusters.AddFirst(partitions[i]._clusters[j]);
                }
            }
            m_clsarray = m_clusters.ToArray();
            m_distancesMatrix = new double[m_clsarray.Length, m_clsarray.Length];

            // reset matrix
            for (int i = 0; i < m_clsarray.Length; i++)
            {
                for (int j = 0; j < m_clsarray.Length; j++)
                {
                    m_distancesMatrix[i, j] = 0;
                }
            }

            // creata data
            m_metadata = new AData[m_clsarray.Length];
            for (int i = 0; i < m_metadata.Length; i++)
            {
                m_metadata[i] = new AData(0);
                m_metadata[i].id = i;
            }

            // calculate actual distance
            for (int i = 0; i < m_clsarray.Length; i++)
            {
                for (int j = 0; j < m_clsarray.Length; j++)
                {
                    m_distancesMatrix[i, j] = m_clsarray[i].compareTo(m_clsarray[j]);
                }
            }

        } // end of createHyperGraph

        /**
         * second stage is to cluster Hyper Edges.
         * @param k (int) [IN] the number of clusters.
         */
        private void clusterHyperEdges( int k)
        {
           // m_distancesMatrix;
           // m_metadata;
            ISimilarity tmpFunc = new CMatrixBasedDistance(m_distancesMatrix);
            CPartition tmpPartition = m_clusterer.cluster(m_metadata,tmpFunc,k);
            m_metaClusters = tmpPartition._clusters.ToArray();
        }

        /**
         * Third stage, collapse Hyper Edges.
         */
        private void collapseHyperEdges()
        {
            // m_dataAfilliation actually indicates how much each datum belongs to each metaCluster
            m_dataAfilliation = new uint[m_data.Length, m_metaClusters.Length];

            // init matrix
            for (int i = 0; i < m_data.Length; i++)
            {
                for (int j = 0; j < m_metaClusters.Length; j++)
                {
                    m_dataAfilliation[i, j] = 0;
                }
            }

            for (int i = 0; i < m_data.Length; i++)  // for each pixel
            {
                for (int j = 0; j < m_metaClusters.Length; j++) // for each meta cluster
                {
                    CCluster metaCls = m_metaClusters[j];

                    foreach (AData cls in metaCls.data)         // for each cluster (metadata) in meta cluster
                    {
                        if (m_clsarray[cls.id].data.Contains(m_data[i]))
                        {
                            // if cluster is contained
                            m_dataAfilliation[i, j]++; // metaCls._data.Count
                        }
                           
                    }
                }
            }



        }
    
        /**
         * fourth (and final) step is that each meta-cluster now competes for objects.
         */
        private void competeForObjects()
        {
            int bestcluster = 0;
            uint bestSum ;
            for (int i = 0; i < m_data.Length; i++)  // for each pixel
            {
                bestSum = 0;
                bestcluster = 0;
                for (int j = 0; j < m_metaClusters.Length; j++) // for each meta cluster
                {
                    if (m_dataAfilliation[i, j] > bestSum)
                    {
                        bestcluster = j;
                        bestSum = m_dataAfilliation[i, j];
                    }
                } // end of find max value and it's index

                // add data i to best fitting cluster.
                m_resultPartition.clusters[bestcluster].data.Add(m_data[i]);

            } // end of loop - per pixel

        }
    }
}
